#!/usr/bin/python
###
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# VideoArea and VideoWidget from Listen
# Listen is the legal property of mehdi abaakouk <theli48@gmail.com>
# Copyright (c) 2006 Mehdi Abaakouk
###
try:
 	import pygtk
  	pygtk.require("2.0")
except:
  	pass
try:
	import gtk
  	import gtk.glade
  	import gobject
except:
	sys.exit(1)

import os,md5
#from mplayer import *
import pygst
pygst.require("0.10")
import gst, cue

COLUMN_TRACK = 0
COLUMN_LENGTH = 1
COLUMN_TITLE = 2
COLUMN_ALBUM = 3


class Gui:

    cues = {}
    current_cue = None
    current_track = None
    last_index = 0
        
    statuses = {'buffer':'','pos':'00:00','dur':'00:00'}
    _query_id = -1
    cur_index = -1
    paused = False
    count = 0
    seeking = False # User is seeking, stop updating seeker from timer
    update_seeker = False # Update seeker to current position, from timer
    can_seek = False
    do_seek = None
    metadata = {} #Tags

    def getlibpath(self,file):
        for i in sys.path:
            print i+file
            if os.path.exists(i+file):
                return i+file

    def __init__(self):
        #Set the Glade file
        self.gladefile = self.getlibpath("/cueplayer/gui/gui.glade")  
        self.wTree = gtk.glade.XML(self.gladefile)
        #Get the Main Window, and connect the "destroy" event
        self.window = self.wTree.get_widget("window")
        if (self.window):
            self.window.connect("destroy", self.destroy)
        
        self.treeview = self.wTree.get_widget("treeview1")
        self.status = self.wTree.get_widget("status")
        self.seeker = self.wTree.get_widget("seeker")
        
        self.player = gst.element_factory_make("playbin", "player")
        
        #self.gconfsink = gst.element_factory_make("fakesink", "my-videosink")
        #self.player.set_property("video-sink", self.gconfsink)
        
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("message::tag", self.on_tag_message)
        bus.connect("sync-message::element", self.on_sync_message)
        
        self.time_format = gst.Format(gst.FORMAT_TIME)
        #self.time_format_percent = gst.Format(gst.FORMAT_PERCENT)
        #self.time_format_bytes = gst.Format(gst.FORMAT_BYTES)
        '''
        r = gst.registry_get_default()
        l = [x for x in r.get_feature_list(gst.ElementFactory) \
             if (gst.ElementFactory.get_klass(x) == "Visualization")]
        
        vis = 0
        if (l != []):
            import random
            vis = random.randrange(0, len(l) ) # maybe len(l)-1
            print "Visualizer:",l[vis].get_name()
            visualizer = gst.element_factory_make(l[vis].get_name())
            self.player.set_property("vis-plugin", visualizer) # If no video, show vis
        '''
        
        dic = { "on_ava1_activate" : self.on_open,
            "on_lopeta1_activate" : gtk.main_quit,
            "on_clearlist_activate" : self.clear_list,
            "on_treeview1_row_activated" : self.row_activated,
            "on_button2_clicked" : self.on_start_pause,
            "on_button3_clicked" : self.on_stop,
            "on_button1_clicked": self.on_prev,
            "on_button4_clicked": self.on_next,
            "on_vol_value_changed": self.on_vol_changed,
            "on_seeker_value_changed": self.on_seeker_value_changed,
            "on_seeker_move_slider": self.on_seeker_move_slider,
            "on_seeker_press_event": self.on_seeker_press_event,
            "on_seeker_release_event": self.on_seeker_release_event }
            
        self.wTree.signal_autoconnect(dic)
        self.add_columns()
        self.set_status()
        gtk.gdk.notify_startup_complete()


    def set_btn_label(self,btn, text=None, stock=None):
        #label = self.wTree.get_widget(btn+"label")
        #label.set_label(text)
        if stock:
            self.wTree.get_widget(btn+"image").set_from_stock( stock,gtk.ICON_SIZE_BUTTON)
            
    def set_play_play(self):
        self.set_btn_label("play","Play",gtk.STOCK_MEDIA_PLAY)
        
    def set_play_pause(self):
        self.set_btn_label("play","Pause",gtk.STOCK_MEDIA_PAUSE)
        
    #def get_btn_label(self, btn="play"):
    #    return self.wTree.get_widget(btn+"label").get_label()
        
    def set_status(self):
        self.status.set_label( "%s  %s/%s"%( self.statuses['buffer'],
            self.statuses['pos'],self.statuses['dur'] ) )
    
    def on_seeker_press_event(self,w, e = None):
        #print "Drag",w,e
        self.seeking = True
        
    def on_seeker_release_event(self,w, e = None):
        #print "Drag",w,e
        self.seeking = False
        
    def on_seeker_move_slider(self,w,v):
        #print "Move slider",w,v
        self.update_seeker = True
        if v == gtk.SCROLL_PAGE_DOWN:
            self.set_seek( self.get_position() + 30 )
        elif v == gtk.SCROLL_PAGE_UP:
            self.set_seek( self.get_position() - 30 )
        elif v == gtk.SCROLL_STEP_RIGHT:
            self.set_seek( self.get_position() + 5 )
        elif v == gtk.SCROLL_STEP_LEFT:
            self.set_seek( self.get_position() - 5 )
        elif v == gtk.SCROLL_STEP_UP:
            self.set_seek( self.get_position() - 10 )
        elif v == gtk.SCROLL_STEP_DOWN:
            self.set_seek( self.get_position() + 10 )
        self.update_seeker = False
        #<enum GTK_SCROLL_STEP_UP , PAGE_UP of type GtkScrollType>

                
    def on_seeker_value_changed(self,w): #Watch out for recursion
        if not self.update_seeker and self.can_seek:
            self.set_seek(w.get_value())
    
    def set_seek(self, pos):
        seek_ns = pos * 1000000000
        if not self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, seek_ns):
            print "Cannot seek" #Seems like bug to me, with http gstreamer should check if server supports ranges :(
            
    def get_duration_ns(self):
        return self.player.query_duration(self.time_format, None)[0]
        
    def get_duration(self):
        return self.get_duration_ns() / 1000000000
        
    def get_duration_bytes(self):
        return self.player.query_duration(self.time_format_bytes, None)[0]
    
    def get_position_ns(self):
        return self.player.query_position(self.time_format, None)[0]

    def get_position(self):
        return self.player.query_position(self.time_format, None)[0]/ 1000000000

    def get_position_bytes(self):
        return self.player.query_position(self.time_format_bytes, None)[0]
        
    def on_vol_changed(self,w):
        self.set_vol(w.get_value())
    
    def set_vol(self,vol = None):
        if not vol:
            vol = self.wTree.get_widget("volume").get_value()
        self.player.set_property("volume",vol)
    
    def on_tag_message(self, bus, message):
        tags = message.parse_tag()
        for x in tags.keys():
            print x,"=",tags[x]
            if x != "private-id3v2-frame":
                self.metadata[x] = tags[x]
    
    def on_message(self, bus, message):
        t = message.type
        #print "Message of",t
        if t == gst.MESSAGE_EOS:
            self.player.set_state(gst.STATE_NULL)
            #self.set_play_play()
            self.on_next(None)
            
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            print message
            self.set_play_play()
        #elif t == gst.MESSAGE_TAG:
        #    tags = message.parse_tag()
        #    for x in tags.keys():
        #        print x,"=",tags[x]
            
        elif t == gst.MESSAGE_BUFFERING:
            self.statuses['buffer'] = "%d%%"%message.parse_buffering()
            self.set_status()
            
        elif t == gst.MESSAGE_STATE_CHANGED:
            old,new,pending = message.parse_state_changed()
            
            if new == gst.STATE_PLAYING:
                if self.do_seek:
                    self.set_seek(self.do_seek)
                    self.do_seek = None
                    
                pos_int = self.get_duration()

                if pos_int > -1:
                            self.statuses['dur'] = self.convert(pos_int)
                            self.seeker.set_range(0, pos_int)
                            self.can_seek = True
                else:
                            self.statuses['dur'] = '00:00'
                            self.seeker.set_range(0, 1)
                            self.can_seek = False
                self.set_status()
                self.set_vol()
                
                
                def set_last(model, path, iter, time):
                    nr = model.get_value(iter,0)
                    cue = model.get_value(iter,4)
                    #print nr, len(self.current_cue.tracks) 
                    if cue == self.current_cue and nr == len(self.current_cue.tracks):
                        t = self.convert(time - self.current_cue.tracks[nr].indexes[1] / 75)
                        model.set_value(iter,1,t)
                    
                self.treeview.get_model().foreach(set_last, self.get_duration() )
                
                #self.set_tree_file_length(self.statuses['dur'])
            elif old == gst.STATE_NULL:
                self.metadata.clear()
                self.statuses = {'buffer':'','pos':'00:00','dur':'00:00'}
                self.set_status()

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        #message_name = message.structure.get_name()
        #if message_name == "prepare-xwindow-id":
        #    imagesink = message.src
        #    imagesink.set_property("force-aspect-ratio", True)
        #    self.vwidget.set_sink(imagesink)

    def on_start_pause(self,w):
        
        if self.player.get_state()[1] != gst.STATE_NULL:
            if self.player.get_state()[1] == gst.STATE_PLAYING:
                self.set_play_play()
                self.player.set_state(gst.STATE_PAUSED)
                self.paused = True
            else:
                self.player.set_state(gst.STATE_PLAYING)
                self.set_play_pause()
                self.paused = False
        else:
            f,i = self.get_cuesheet((0,))
            self.play_track(f,i)
            #self.on_next(None)
            
    def on_stop(self,w):
        try:
            if self.player:
                self.player.set_state(gst.STATE_NULL)
                self.set_play_play()
                self._stop_queries()
        except:
            print "Error on Stop"
            
    def on_next(self,w):
        self.cur_index += 1
        if self.cur_index >= self.count:
            self.cur_index = 0
        f,i = self.get_cuesheet((self.cur_index,))
        self.play_track(f,i)
        
        
    def on_prev(self,w):
        self.cur_index -= 1
        if self.cur_index < 0:
            self.cur_index = self.count - 1
        f,i = self.get_cuesheet((self.cur_index,))
        self.play_track(f,i)


    def play_track(self, cue, track):
        '''
        if not cue:
            path = self.get_file_selected()
            if not path:
                path = self.get_file_path((0))
        '''
        if cue:
            self.current_cue = cue
            self.current_track = track
            print "Cue:", cue.attribs['TITLE']
            print "File:",self.player.get_property('uri')
            #XXX does it open the new file regardless if we set state to NULL before?
            if self.player.get_property('uri') != "file://"+cue.attribs['FILE'][0]:
                self.player.set_state(gst.STATE_NULL)
            
            self.set_play_pause()
            self.last_index = cue.tracks[track].indexes[1]
            self.do_seek = seek_time = self.convert_f2ns(cue.tracks[track].indexes[1])
            self.set_seek(seek_time)
            
            print "Play:",cue.attribs['FILE'][0]
            self.player.set_property('uri', "file://"+cue.attribs['FILE'][0] ) #"file://" + filepath)
            self.player.set_state(gst.STATE_PLAYING)

            self._start_queries()
            if self.cur_index > -1:
                self.treeview.set_cursor((self.cur_index,))
            self.paused = False
            #self.metadata.clear()

    def add_columns(self):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Track', renderer, text=COLUMN_TRACK)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Length', renderer, text=COLUMN_LENGTH)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)
        
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Title', renderer, text=COLUMN_TITLE)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Album', renderer, text=COLUMN_ALBUM)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)        
        
        
    def row_activated(self,w,path,view_column):#user dbl clicked treeview
        f,i = self.get_cuesheet(path)
        self.play_track(f,i)
        
    def get_cuesheet_selected(self):
        model,iter = self.treeview.get_selection().get_selected()
        if model and iter:
            self.cur_index = model.get(iter,0)[0] - 1
            return model.get_value(iter,4), model.get_value(iter,0)
        return None

        
    def get_cuesheet(self,path):
        model = self.treeview.get_model()
        if model:
            print path
            iter = model.get_iter(path)
            self.cur_index = model.get(iter,0)[0] - 1
            return model.get(iter,4)[0], model.get(iter,0)[0]
        return None
    
    def convert_ns(self, time_int):
        return self.convert(time_int / 1000000000)

    def convert(self, time_int):
        sec = time_int % 60
        min = time_int % 3600 / 60
        hour = time_int / 3600
        if hour == 0:
            return "%02d:%02d"%(min,sec)
        return "%d:%02d:%02d"%(hour,min,sec)
        
    def convert_f2ns(self, frames):
        return (frames/ 75.0) #* 1000000000  #XXX  ???
        
    def convert_from_frame(self, time_int):
        frame = time_int % 75
        sec = time_int % (60*75) / 75
        min = time_int % (3600*75) / (60*75)
        hour = time_int / (3600*75)
        if hour == 0:
            return "%02d:%02d"%(min,sec)
        return "%d:%02d:%02d"%(hour,min,sec)

    def clear_data(self):
        ls = gtk.ListStore(gobject.TYPE_UINT, gobject.TYPE_STRING,
                gobject.TYPE_STRING,gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)        
        self.treeview.set_model(ls)
    
    def set_tree_file_length(self,time):
        try:
            ls = self.treeview.get_model()
            iter = ls.get_iter( (self.cur_index ,) )
            ls.set_value(iter,1,time)
        except:
            print "Failed to set treeview time"
            
    def search_track(model, path, iter, user_time):
        print nr
        nr = model.get_value(iter,0)
        
        if self.current_cue.tracks.has_key(nr) and self.current_cue.tracks.has_key(nr+1):
            idx0 = self.current_cue.tracks[nr].indexes[1]
            idx1 = self.current_cue.tracks[nr + 1].indexes[1]
            
            if idx0 >= user_time and user_time < idx1 and idx0 != self.last_index:
                print "yes"
                self.last_index = idx0
                self.treeview.set_cursor(path)
            
        pass

            
    def change_visualisation(self,widget,vis_name): 
        #Some not so mature vis plugins hang the pipeline 
        #when changed while in Playing state
        vis = None
        if vis_name!=None:
            #try: 
            vis = gst.element_factory_make(vis_name)
            #except Exception,e: 
            #    vis = None
            
            if vis:
                self.player.set_property('vis-plugin',vis)
        else:
            self.player.set_property('vis-plugin',None)
        
    def destroy(klass, widget=None):
        klass.on_stop(None)
        gtk.main_quit()
    
    def clear_list(self,w):
        self.clear_data()
        
    def on_open(self,w):
        f = gtk.FileChooserDialog("Open CUE", self.window, gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons = ( gtk.STOCK_CANCEL,gtk.RESPONSE_CLOSE,
                        gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        filter = gtk.FileFilter()
        filter.set_name("CUE")
        filter.add_pattern("*.cue")
        f.add_filter(filter)

        f.connect("response",self.dialog_response)
        f.show_all()
    
    def dialog_response(self,w,s):
        if s == gtk.RESPONSE_OK:
            if w.get_filter().get_name() == "CUE":
                self.open_file(w.get_filename())
                
        w.destroy()
    
    
    def open_file(self,file):
        cs = cue.read_cuesheet(file)
        path = os.path.dirname(file)
        print cs.attribs['FILE'][0]
        if os.path.dirname(cs.attribs['FILE'][0]) == '':
            cs.attribs['FILE'] = ("%s/%s"%(path,cs.attribs['FILE'][0]), cs.attribs['FILE'][1])
            
        print "Cue file:", cs.attribs['FILE'][0]
        
        hash = md5.new()
        hash.update(file)
        
        if not self.cues.has_key(hash.hexdigest()):
            #self.cues[hash.hexdigest()] = cs
            self.count += 1

            ls = self.treeview.get_model()
            if not ls:
                ls = gtk.ListStore(gobject.TYPE_UINT, gobject.TYPE_STRING,
                        gobject.TYPE_STRING,gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)
                        
            for i in cs.tracks:
                len = '00:00'
                track = cs.tracks[i]
                self.count += 1 #TODO: Track count in treeview ListStore

                if cs.tracks.has_key(track.number+1):
                    nxt_idx = cs.tracks[track.number+1].indexes[1]
                    len = self.convert_from_frame(nxt_idx - track.indexes[1])
                    
                title = ""
                if track.attribs.has_key('TITLE'):
                    title = track.attribs['TITLE'] + " - "
                if track.attribs.has_key('PERFORMER'):
                    title += track.attribs['PERFORMER']
                    
                iter = ls.append()                    
                ls.set(iter, 0, track.number, 1, len, 2, title, 3, cs.attribs['TITLE'] ,4,cs)
                self.treeview.set_model(ls)

    def _start_queries(self):
        def do_query():
            try:
                pos_int = self.get_position_ns()
                self.statuses['pos'] = self.convert_ns(pos_int)
                self.set_status()
                pos_int /= 1000000000
                if not self.seeking and self.can_seek:
                    self.update_seeker = True
                    self.seeker.set_value(pos_int)
                    self.update_seeker = False
                    #gtk.gdk.thread_enter()/leave()
                    #print "Do search"
                    ls = self.treeview.get_model()

                    def search_track(model, path, iter, user_time):
                        nr = model.get_value(iter,0)
                        cue = model.get_value(iter,4)
                        if self.current_cue != cue:
                            return
                        if self.current_cue.tracks.has_key(nr):
                            idx0 = self.current_cue.tracks[nr].indexes[1]
                            idx1 = -1
                            if self.current_cue.tracks.has_key(nr+1):
                                idx1 = self.current_cue.tracks[nr + 1].indexes[1]
                            
                            if idx0 <= user_time and (user_time < idx1 or idx1 == -1) and self.last_index != idx0:
                                #print "yes", idx0,user_time, idx1
                                self.last_index = idx0
                                self.treeview.set_cursor(path)
                        
                    ls.foreach(search_track,  pos_int*75  )
            except:
                # print 'query failed'
                pass
            return True
        if self._query_id == -1:
            self._query_id = gobject.timeout_add(500, # 2 Hz
                                                 do_query)

    def _stop_queries(self):
        if self._query_id != -1:
            gobject.source_remove(self._query_id)
            self._query_id = -1



#from __future__ import with_statement
import sys,linecache
def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        filename = frame.f_globals["__file__"]
        if filename == "<stdin>":
            filename = "glade.py"
        if (filename.endswith(".pyc") or
            filename.endswith(".pyo")):
            filename = filename[:-1]
        name = frame.f_globals["__name__"]
        line = linecache.getline(filename, lineno)
        print "%s:%s: %s" % (name, lineno, line.rstrip())

    return traceit
    
if __name__ == "__main__":
    #sys.settrace(traceit)
    g = Gui()
    gtk.gdk.threads_init()
    gtk.main()
